package org.finesys.common.netty.websocket.support;

import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import java.util.HashMap;
import java.util.Map;

import javax.net.ssl.SSLException;

import org.finesys.common.netty.websocket.stomp.support.StompWebSocketEndpointEventServer;
import org.finesys.common.netty.websocket.stomp.support.StompWebSocketInitializer;
import org.springframework.beans.factory.DisposableBean;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.WriteBufferWaterMark;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.util.concurrent.DefaultEventExecutorGroup;
import io.netty.util.concurrent.EventExecutorGroup;
import io.netty.util.internal.logging.InternalLogger;
import io.netty.util.internal.logging.InternalLoggerFactory;

/**
 * WebSocket服务器管理类，创建websocket服务和优雅的关闭服务
 */
public class WebSocketServer implements DisposableBean {
    private final WebSocketEndpointEventServer webSocketEndpointEventServer;
    private final StompWebSocketEndpointEventServer stompWebSocketEndpointEventServer;
    private final WebSocketEndpointConfig webSocketEndpointConfig;
    //日志记录器，用于日志输出
    private static final InternalLogger INTERNAL_LOGGER = InternalLoggerFactory.getInstance(WebSocketServer.class);
    // EventExecutorGroup配置
    private EventExecutorGroup eventExecutorGroup;
    // 创建Boss的EventLoopGroup
    private EventLoopGroup bossLoopGroup;
    // 创建Worker的EventLoopGroup
    private EventLoopGroup workerLoopGroup;

    public WebSocketServer(WebSocketEndpointEventServer webSocketEndpointEventServer, StompWebSocketEndpointEventServer stompWebSocketEndpointEventServer, WebSocketEndpointConfig webSocketEndpointConfig) {
        this.webSocketEndpointEventServer = webSocketEndpointEventServer;
        this.stompWebSocketEndpointEventServer = stompWebSocketEndpointEventServer;
        this.webSocketEndpointConfig = webSocketEndpointConfig;
    }

    /**
     * 初始化WebSocket服务器
     * <p>
     * 该方法负责初始化WebSocket服务器，包括配置SSL、跨域、EventExecutorGroup以及Boss和Worker的EventLoopGroup。
     *
     * @throws SSLException 如果SSL配置过程中出现异常
     */
    public void init() throws SSLException {
        // EventExecutorGroup配置
        if (webSocketEndpointConfig.isUseEventExecutorGroup()) {
            int threads = webSocketEndpointConfig.getEventExecutorGroupThreads();
            eventExecutorGroup = new DefaultEventExecutorGroup(threads > 0 ? threads : 16);
        } else {
            // 如果不使用EventExecutorGroup，则设置为null
            eventExecutorGroup = null;
        }
        // 创建Boss和Worker的EventLoopGroup
        bossLoopGroup = new NioEventLoopGroup(webSocketEndpointConfig.getBossLoopGroupThreads());
        workerLoopGroup = new NioEventLoopGroup(webSocketEndpointConfig.getWorkerLoopGroupThreads());

        try {
            ServerBootstrap serverBootstrap;
            //启用stomp协议支持
            if ("wss".equals(webSocketEndpointConfig.getProtocol()) || "stomp".equals(webSocketEndpointConfig.getProtocol())) {
                serverBootstrap = initStompWebSocketServer(webSocketEndpointConfig);
            } else {
                serverBootstrap = initWebSocketServer(webSocketEndpointConfig);
            }
            //绑定到指定主机和端口，或者绑定到所有可用网络接口
            ChannelFuture channelFuture;
            if ("0.0.0.0".equals(webSocketEndpointConfig.getHost())) {
                // 绑定到所有可用网络接口
                channelFuture = serverBootstrap.bind(Integer.parseInt(webSocketEndpointConfig.getPort()));
            } else {
                try {
                    // 绑定到指定主机和端口
                    channelFuture = serverBootstrap.bind(new InetSocketAddress(InetAddress.getByName(webSocketEndpointConfig.getHost()), Integer.parseInt(webSocketEndpointConfig.getPort())));
                } catch (UnknownHostException e) {
                    INTERNAL_LOGGER.error("Unknown host: {}", webSocketEndpointConfig.getHost(), e);
                    // 如果主机名未知，则绑定到默认端口
                    channelFuture = serverBootstrap.bind(Integer.parseInt(webSocketEndpointConfig.getPort())).sync();
                }
            }
            // 添加监听器以处理绑定结果
            channelFuture.addListener(future -> {
                if (!future.isSuccess()) {
                    INTERNAL_LOGGER.error("Failed to bind server", future.cause());
                }
            });

            // 添加JVM关闭钩子以优雅关闭EventLoopGroup和EventExecutorGroup
            Runtime.getRuntime().addShutdownHook(new Thread(() -> {
                bossLoopGroup.shutdownGracefully().syncUninterruptibly();
                workerLoopGroup.shutdownGracefully().syncUninterruptibly();
                if (eventExecutorGroup != null) {
                    eventExecutorGroup.shutdownGracefully().syncUninterruptibly();
                }
            }));

            // 同步等待服务器启动完成
            channelFuture.syncUninterruptibly();
            // 等待服务器关闭，通常不会执行到这里
            channelFuture.channel().closeFuture().syncUninterruptibly();
        } catch (Exception e) {
            INTERNAL_LOGGER.error("Server initialization failed", e);
        }
    }

    private ServerBootstrap initStompWebSocketServer(WebSocketEndpointConfig webSocketEndpointConfig) throws SSLException {
        // 初始化WebSocket服务器，此处省略具体实现细节
        ServerBootstrap serverBootstrap = new ServerBootstrap();
        serverBootstrap.group(bossLoopGroup, workerLoopGroup)
                .channel(NioServerSocketChannel.class)
                //增加日志处理器，用于记录日志
                .handler(new LoggingHandler(logLevel(webSocketEndpointConfig.getLogLevel())))
                .childHandler(new StompWebSocketInitializer(stompWebSocketEndpointEventServer, webSocketEndpointConfig, eventExecutorGroup));
        return serverBootstrap;
    }


    private ServerBootstrap initWebSocketServer(WebSocketEndpointConfig webSocketEndpointConfig) throws SSLException {
        // 初始化WebSocket服务器，此处省略具体实现细节
        ServerBootstrap serverBootstrap = new ServerBootstrap();
        serverBootstrap.group(bossLoopGroup, workerLoopGroup)
                .channel(NioServerSocketChannel.class)
                //连接超时毫秒数
                .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, webSocketEndpointConfig.getConnectTimeoutMillis())
                //Socket参数，服务端接受连接的队列长度，如果队列已满，客户端连接将被拒绝。默认值，Windows为200，其他为128。
                .option(ChannelOption.SO_BACKLOG, webSocketEndpointConfig.getSoBacklog())
                //Netty参数，一个Loop写操作执行的最大次数。
                //默认值为16。也就是说，对于大数据量的写操作至多进行16次，如果16次仍没有全部写完数据，此时会提交一个新的写任务给EventLoop，任务将在下次调度继续执行。这样，其他的写请求才能被响应不会因为单个大数据量写请求而耽误
                .option(ChannelOption.WRITE_SPIN_COUNT, webSocketEndpointConfig.getWriteSpinCount())
                //Netty高低水位设置及默认大小
                .option(ChannelOption.WRITE_BUFFER_WATER_MARK, new WriteBufferWaterMark(webSocketEndpointConfig.getWriteBufferLowWaterMark(), webSocketEndpointConfig.getWriteBufferHighWaterMark()))
                //TCP参数，立即发送数据，默认值为Ture（Netty默认为True而操作系统默认为False）。该值设置Nagle算法的启用，
                // 该算法将小的碎片数据连接成更大的报文来最小化所发送的报文的数量，如果需要发送一些较小的报文，则需要禁用该算法。
                // Netty默认禁用该算法，从而最小化报文传输延时。
                .childOption(ChannelOption.TCP_NODELAY, webSocketEndpointConfig.isTcpNodelay())
                //Socket参数，连接保活，默认值为False。启用该功能时，TCP会主动探测空闲连接的有效性。可以将此功能视为TCP的心跳机制，需要注意的是：默认的心跳间隔是7200s即2小时。Netty默认关闭该功能
                .childOption(ChannelOption.SO_KEEPALIVE, webSocketEndpointConfig.isSoKeepalive())
                //Socket参数，关闭Socket的延迟时间，默认值为-1，表示禁用该功能。-1表示socket.close()方法立即返回，
                // 但OS底层会将发送缓冲区全部发送到对端。0表示socket.close()方法立即返回，OS放弃发送缓冲区的数据直接向对端发送RST包，
                // 对端收到复位错误。非0整数值表示调用socket.close()方法的线程被阻塞直到延迟时间到或发送缓冲区中的数据发送完毕，
                // 若超时，则对端会收到复位错误。
                .childOption(ChannelOption.SO_LINGER, webSocketEndpointConfig.getSoLinger())
                //Netty参数，一个连接的远端关闭时本地端是否关闭，默认值为False。值为False时，连接自动关闭；为True时，触发ChannelInboundHandler的userEventTriggered()方法，事件为ChannelInputShutdownEvent。
                .childOption(ChannelOption.ALLOW_HALF_CLOSURE, webSocketEndpointConfig.isAllowHalfClosure())
                //增加日志处理器，用于记录日志
                .handler(new LoggingHandler(logLevel(webSocketEndpointConfig.getLogLevel())))
                .childHandler(new WebSocketInitializer(webSocketEndpointEventServer, webSocketEndpointConfig, eventExecutorGroup));
        //Socket参数，TCP数据接收缓冲区大小。该缓冲区即TCP接收滑动窗口，linux操作系统可使用命令：cat /proc/sys/net/ipv4/tcp_rmem查询其大小。一般情况下，该值可由用户在任意时刻设置，但当设置值超过64KB时，需要在连接到远端之前设置。
        if (webSocketEndpointConfig.getSoRcvbuf() != -1) {
            serverBootstrap.childOption(ChannelOption.SO_RCVBUF, webSocketEndpointConfig.getSoRcvbuf());
        }
        //Socket参数，TCP数据发送缓冲区大小。该缓冲区即TCP发送滑动窗口，linux操作系统可使用命令：cat /proc/sys/net/ipv4/tcp_smem查询其大小。
        if (webSocketEndpointConfig.getSoSndbuf() != -1) {
            serverBootstrap.childOption(ChannelOption.SO_SNDBUF, webSocketEndpointConfig.getSoSndbuf());
        }
        return serverBootstrap;
    }

    /**
     * 根据传入的日志级别字符串返回对应的LogLevel枚举值
     *
     * @param level 日志级别字符串，支持大小写混合
     * @return LogLevel枚举值，如果传入的级别字符串无效，则默认返回LogLevel.ERROR
     */
    private LogLevel logLevel(String level) {
        // 将日志级别字符串转换为大写
        level = level.toUpperCase();
        // 使用Map简化日志级别的匹配过程
        Map<String, LogLevel> levelMap = new HashMap<>();
        levelMap.put("TRACE", LogLevel.TRACE);
        levelMap.put("DEBUG", LogLevel.DEBUG);
        levelMap.put("INFO", LogLevel.INFO);
        levelMap.put("WARN", LogLevel.WARN);
        levelMap.put("ERROR", LogLevel.ERROR);

        // 返回对应的LogLevel，如果未找到则默认返回LogLevel.ERROR
        return levelMap.getOrDefault(level, LogLevel.ERROR);
    }


    /**
     * 获取 WebSocket 终端事件服务器
     *
     * @return WebSocket终端事件服务器实例
     */
    public WebSocketEndpointEventServer getWebSocketEndpointEventServer() {
        return webSocketEndpointEventServer;
    }

    public StompWebSocketEndpointEventServer getStompWebSocketEndpointEventServer() {
        return stompWebSocketEndpointEventServer;
    }

    /**
     * 销毁方法，优雅地关闭EventLoopGroup
     *
     * @throws Exception 抛出异常
     */
    @Override
    public void destroy() throws Exception {
        // 确保EventLoopGroup被优雅关闭
        if (bossLoopGroup != null && !bossLoopGroup.isShutdown()) {
            bossLoopGroup.shutdownGracefully();
        }
        if (workerLoopGroup != null && !workerLoopGroup.isShutdown()) {
            workerLoopGroup.shutdownGracefully();
        }
        if (eventExecutorGroup != null && !eventExecutorGroup.isShutdown()) {
            eventExecutorGroup.shutdownGracefully();
        }
    }
}
